#!/usr/bin/perl
#
# Copyright 2008-2010 Vadim Zhukov <persgray@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY AGAVA COMPANY ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL THE AGAVA COMPANY OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation are
# those of the authors and should not be interpreted as representing official
# policies, either expressed or implied, of the AGAVA Company.

use bytes;
use strict;

use Socket;
use RB::Dir;
use RB::Profile;
use RB::Util;


sub usage(;$) {
	my $msg = shift;
	if ($msg ne '') {
		print STDERR $msg."\n\n";
	}
	print STDERR <<EOU;
usage: rb_cpanel info    host client  date [mysql[=database] ...] [files]
       rb_cpanel list    host client [date [mysql[=database] ...] [files]]
       rb_cpanel show    host client [date [mysql[=database] ...] [files]]
       rb_cpanel put     host client  date [mysql[=database] ...] [files]
       rb_cpanel restore host client  date [mysql[=database] ...] [files]
"show" is just an alias for "list".
Date should be in YYYYMMDD or DD.MM.YYYY format.
Database name may but not should include client name prefix ("client0_").
Host names can be specified in any unambigious form.
EOU
	exit ($msg eq '' ? 0 : 1);
}

if (scalar(@ARGV) == 0) {
	usage;
}

# Fixed profile
our $Profile = RB::Profile->new($ENV{'HOME'}.'/.rb/cpanel');
our $Config = $Profile->{'global'};

my ($Command, $Host, $Client, $Date) =
    (lc shift @ARGV, lc shift @ARGV, shift @ARGV, shift @ARGV);
$Command = 'list' if $Command eq 'show';
unless (($Command eq 'list' and defined $Client) or defined $Date) {
	usage "\"list\" command requires at least host and client; others - "
	    ."same plus date.";
}

if ($Command ne 'info' and $Command ne 'list' and $Command ne 'put' and
    $Command ne 'restore') {
	usage "Illegal command: $Command";
}

# Check host validity
sub sayInvalidHost() {
	print STDERR "Not a valid host: $Host\n";
	exit 1;
}

if ($Host !~ /^cp[0-9a-z]+(\.agava\.net\.?)?$/) {
	if ($Host !~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) {
		# Not an IP, do straight lookup first
		my @addrs = gethostbyname($Host);
		splice(@addrs, 0, 4);
		unless (@addrs) { sayInvalidHost; }
		$Host = $addrs[0];    # HACK: only first address'll be tried.
	}
	# Now we have an IP address, do reverse lookup
	($Host) = gethostbyaddr($Host, AF_INET);
}
if ($Host !~ /^cp[0-9a-z]+(\.agava\.net\.?)?$/) {
	sayInvalidHost;
}
if ($1 eq '') {
	$Host .= '.agava.net';
} elsif ($1 =~ /\.$/) {
	# We use non-"full" names
	chop $Host;
}

# Check client name validity
if ($Client eq '' or length($Client) > 8 or $Client =~ /[^a-z0-9]/) {
	print STDERR "Not a valid client name: $Client\n";
	exit 1;
}

# Check date validity
if (defined $Date and $Date !~ /^[0-9]{8}$/) {
	if ($Date =~ /^([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{4})$/) {
		$Date = sprintf "%04d%02d%02d", $3, $2, $1;
	} else {
		print STDERR "Not a valid date: $Date. ".
		    "Should be in YYYYMMDD or DD.MM.YYYY format.";
		exit 1;
	}
}

# Determine rb_restore location based on our.
my $prog = $0;
$prog =~ s|^(.*/)?[^/]+$|$1rb_restore|;

my @Cmds;

# FIXME: We should support using wildcards in rb_restore(1)
# commands correctly instead.
sub getAllDBList() {
	unless (opendir(MYSQLDBS,
	    glob($Config->{'archive_dir'})."/${Host}/mysql")) {
		print STDERR "Cannot open list of MySQL databases: $!\n";
		exit 2;
	}
	my @dbs = grep { /^$Client.'_.*'/ } readdir MYSQLDBS;
	closedir MYSQLDBS;
	return @dbs;
}

if ($Command eq 'list' and !defined $Date) {
	# Show all dates for which backups exist
	push(@Cmds, {
	    'cmd' => [ 'list', 'cpanel', $Host, 'mysql', "${Client}_.*" ],
	    'desc' => "List of MySQL database archives of client $Client on $Host"
	    });
	push(@Cmds, {
	    'cmd' => [ 'list', 'cpanel', $Host, 'site', "${Client}" ],
	    'desc' => "List of file archives of client $Client on $Host"
	    });
} else {
	my (@Databases, @Files);
	my ($allFiles, $allDB) = (0, 0);
	if (@ARGV) {
		while (@ARGV) {
			my $item = shift @ARGV;
			if ($item eq 'files') {
				next if $allFiles;
				push(@Files, $Client);
				$allFiles = 1;
			} elsif ($item =~ /^mysql(=(.+))$/) {
				next if $allDB;
				if ($2 ne '') {
					if (substr($item, 0, length($Client)+1)
					    ne $Client.'_') {
						$item = $Client.'_'.$item;
					}
					push(@Databases, $item);
				} else {
					@Databases = getAllDBList();
					$allDB = 1;
				}
			} else {
				usage "Illegal item: $item";
			}
		}
	} else {
		@Databases = getAllDBList();
		@Files = $Client;
	}

	my @cmdBase = ($Command);
	if ($Command eq 'put' or $Command eq 'restore') {
		# Date is required for those
		push(@cmdBase, 'date='.$Date);
	}
	if ($Command eq 'put') {
		push(@cmdBase, 'as=root:'.$Client);
	}
	push(@cmdBase, 'cpanel', $Host);

	if ($Command eq 'info') {
		my @cmd = @cmdBase;
		my $desc = "Information for";
		if (@Databases) {
			push(@cmd, map { 'mysql/'.$_.'/'.$Date } @Databases);
			$desc .= ' MySQL database';
		}
		if (@Files) {
			push(@cmd, map { 'site/'.$_.'/'.$Date } @Files);
			$desc .= ' and' if @Databases;
			$desc .= ' files';
		}
		push(@Cmds, {
		   'cmd' => \@cmd,
		   'desc' => $desc." archives of client $Client on $Host" });
	} else {
		if (@Databases) {
			push(@Cmds, {
			    'cmd' => [ @cmdBase, 'mysql', @Databases ],
			    'desc' => $Command." MySQL database archives of ".
			              "client $Client on $Host"
			    });
		}
		if (@Files) {
			push(@Cmds, {
			    'cmd' => [ @cmdBase, 'site', @Files ],
			    'desc' => $Command." files archives of ".
			              "client $Client on $Host"
			    });
		}
	}
}

foreach my $cmd (@Cmds) {
#print STDERR "CMD: ".join(" ", @{$cmd})."\n";
	print STDERR $cmd->{'desc'}.":\n";
	my $pid = fork();
	unless (defined $pid) {
		print STDERR "Cannot fork new process: $!\n";
		exit $!;
	}
	if ($pid == 0) {
		# Child
		exec($prog, '-q', @{$cmd->{'cmd'}});
		print STDERR "Cannot start restore engine: $!\n";
	} else {
		waitpid($pid, 0);
		exit $? if $?;
	}
}
